home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Pascal Super Library
/
Pascal Super Library (CW International)(1997).bin
/
DBASE_UT
/
TPDB335
/
TPDB.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1993-11-26
|
50KB
|
1,831 lines
{$A+,B-,D+,E+,F+,I+,L+,N-,O+,R-,S+,V-}
{$M 16384,0,655360}
unit TPDB;
{This version is Version 3.35 November, 1993}
(***********************************)
(* TPDB *)
(***********************************)
(* Object -Oriented *)
(* Borland/Turbo Pascal Units *)
(* for Accessing dBASE III *)
(* files. *)
(* Copyright 1988 - 1993 *)
(* Brian Corll *)
(* All Rights Reserved *)
(***********************************)
(* FREEWARE *)
(***********************************)
(* dBASE is a registered *)
(* trademark of Borland Int. Inc. *)
(* Version 3.35 November, 1993 *)
(***********************************)
(* Portions Copyright 1984,1991 *)
(* Borland International Corp. *)
(***********************************)
interface
uses
{$IFDEF WINDOWS}
WinCrt,
WinDos,
{$ELSE}
Crt, Dos,
{$ENDIF}
TPDBINDX, TPDBDate, TPDBScrn, TPDBStr;
(******************************)
(* Global VARiables *)
(******************************)
const
(**************************************************************************)
MaxInds = 10; {Maximum number of indexes per file. Change this as needed.}
(**************************************************************************)
AutoWrap: boolean = False;
CursorDown = ^X;
CursorEND = ^F;
CursorHome = ^A;
CursorLeft = ^S;
CursorRight = ^D;
CursorUp = ^E;
DelKey = ^G;
Escape = ^[;
ExtKey: boolean = False;
Filler: char = #32;
MaxLong = 2147483647;
MaxReal = 3.4E37;
MinLong = - 2147483647;
MinReal = 1.5E-45;
NoDuplicates = 0;
Duplicates = 1;
PageDown = ^C;
PageUp = ^R;
Return = ^M;
TabKey = #9;
UpperCase: boolean = False;
{Date format constants}
{Used by SetDateFormat procedure}
French = 1; {dd/mm/yy}
German = 2; {dd.mm.yy}
Italian = 3; {dd-mm-yy}
American = 4; {mm/dd/yy}
British = 5; {dd/mm/yy}
Ansi = 99; {yy.mm.dd}
type
Str2 = string [2];
Str4 = string [4];
Str5 = string [5];
Str6 = string [6];
Str8 = string [8];
Str10 = string [10];
Str15 = string [15];
Str20 = string [20];
Str30 = string [30];
Str60 = string [60];
Str80 = string [80];
Str132 = string [132];
Str254 = string [254];
CharSet = set of char;
ByteSet = set of byte;
FileName = string [66];
DBHeader = record
DBType: byte;
Year: byte;
Month: byte;
Day: byte;
RecCount: longint;
Location: integer;
RecordLen: integer;
RESERVED: array [1..20] of byte;
Terminator: char;
end;
DBField = record
FieldName: array [1..11] of char;
FieldType: byte;
FieldAddress: longint;
FieldLen: byte;
FieldDec: byte;
RESERVED: array [1..14] of char;
end;
HeadPtr = ^DBHeader;
PosPtr = ^DBEditArray;
FieldPtr = ^FieldArray;
DBEditArray = array [1..2, 1..128] of integer;
FieldArray = array [1..128] of DBField;
DBIndex = record
Ndx: IndexFile;
NdxID: byte;
NdxName: FileName;
Open: boolean;
end;
NdxArray = array [1..MaxInds] of DBIndex;
NdxPtr = ^NdxArray;
(*****************************************************************************)
(* Database File Object Declaration *)
(*****************************************************************************)
DataObject = ^DBF;
DBF = object
DBFName: FileName;
DBFile: file;
Header: HeadPtr;
Fields: FieldPtr;
DBFOpen: boolean;
IndsOpen: boolean;
Indexes: NdxPtr;
CurrNdx : Byte;
DBRecord:Pointer;
DBRecNum: longint;
TotalRecs: longint;
NumFields: byte;
MAlloc: boolean;
Start, Stop: integer;
function Add(Field1, Field2: byte): string;
procedure AddDBKey(NdxID: byte; KeyStr: DBKey);
procedure AddDBRec;
function Allocated: boolean;
procedure AppendBlank;
procedure BailOut;
function BinSearch(FieldNo: byte; Position: integer; SearchKey: DBKey): longint;
function BOF: boolean;
procedure CloseDBIndex(NdxID: byte);
procedure DBReset;
procedure DelDBKey(KeyStr: DBKey; NdxID: byte);
function Deleted: boolean;
procedure Display;
function Divide(Field1, Field2: byte): string;
destructor Done;
function DBEOF: boolean;
function Field(FNo: byte): string;
procedure FillRecs(NumRecs: longint);
procedure Find(NdxID: byte; SearchStr: string);
procedure FlushDB;
procedure Get(FNo, X, Y: byte);
procedure GetDBRec(RecordNumber: longint);
function GetField(RecordNo: longint; FNo: byte): string;
procedure GoBottom;
procedure GoTop;
function IIF(BoolVAR: boolean; IfTRUE, IfFALSE: string): string;
procedure IndexOn(NdxID: byte; NdxName: FileName; NdxField: byte; DupFlag: byte);
function IndsAreOpen: boolean;
constructor Init(DBName: FileName);
function Locate(FieldNo: byte; SearchStr: string): boolean;
procedure LookUp(SearchStr: string; NdxID: byte);
procedure MakeDBIndex(NdxID: byte; DBIndexName: FileName; KeyLen, Status: integer);
procedure Mark;
function Mul(Field1, Field2: byte): string;
procedure NextDBKey(NdxID: byte; KeyStr: DBKey);
procedure NewDBRec;
procedure NextRec;
procedure OpenDBIndex(NdxID: byte; DBIndexName: FileName; KeyLen, Status: integer);
procedure Pack;
procedure PrevDBKey(NdxID: byte; KeyStr: DBKey);
procedure PrevRec;
procedure PutDBRec(RecordNumber: longint);
procedure ReadDBHeader;
procedure Recall;
function RecCount: longint;
function RecNo: longint;
procedure Repl(FNo: byte; InStr: string);
procedure ReplEach(FNo: byte; InStr: string);
procedure Save;
procedure Say(FNo, Row, Col: byte);
procedure SetIndexTo(NdxID : Byte);
procedure ShowStatus;
procedure Skip(NumRecs : Longint);
function Sub(Field1, Field2: byte): string;
function Sum(FNo: byte): real;
procedure WriteDBHeader;
procedure Zap;
end;
(****************************************************************************)
(* END Object Declaration *)
(****************************************************************************)
const
Up: CharSet = [CursorUp];
Down: CharSet = [CursorDown, Return];
Next: CharSet = [Escape];
var
FilesOpen: byte;
UCKey: boolean;
ErrCode: integer;
Found: boolean;
Ch, BC: char;
Normal, Reverse: byte;
Decimals: byte;
TempFile: file;
K: byte;
NumLen: byte;
Y, M, D, DW: word;
FromPack: boolean;
DateFormat: byte;
(**********************************)
(* PROCEDUREs and FUNCTIONs *)
(**********************************)
procedure Beep;
{Sound a couple of tones.}
function BoolToStr(Param: byte; IfTRUE, IfFALSE: char): string;
procedure CheckScreen(var CurrPos: byte; BC: char; Up, Down: CharSet; Low, High: byte);
{Used in full screen editing.}
procedure CopyFile(Source, Dest: FileName);
procedure FlashFill(Row, Col, Rows, Cols, Attr: byte; Ch: char);
{Fill a region of the screen with a specified color and character.}
function GetBoolean(var Param: byte; IfTRUE, IfFALSE: char; X, Y: byte): char;
function GetByte(var Param: byte; LowLim, UpLim, Len, X, Y: byte): char;
function GetInteger(var Param: integer; LowLim, UpLim: integer; Len, X, Y: byte): char;
{Input an integer.}
function GetLongInt(var Param: longint; LowLim, UpLim: longint; Len, X, Y: byte): char;
{Input a long integer.}
function GetReal(var Param: real; LowLim, UpLim: real; Len, X, Y: word): char;
{Input a real number.}
function GetString(var Param: string; Len, X, Y: byte): char;
{Input a string.}
function Input(var S: string; Term: CharSet; L, X, Y: byte; var BC: char): string;
function IntToStr(Number: longint): string;
function Max(N1, N2: integer): integer;
function Min(N1, N2: integer): integer;
procedure Prompt(Row, Col: byte; PromptStr: Str80);
{Display a prompt at a specified row and column.}
function ReadChar: char;
procedure ReadKB(var ExtKey: boolean; var Ch: char);
function RealToStr(Number: real): string;
procedure SetDateFormat(Format: byte);
procedure SetDBColor(FG, BG: byte);
{Set initial foreground and background colors.}
procedure Wait;
{Wait for a key press and display a message.}
implementation
function DBF.Add(Field1, Field2: byte): string; (* Adds two fields and returns the string of the sum. *)
var
T1, T2, T3: string;
A1, A2, A3: real;
ErrCode: integer;
begin
T1 := RTrim(Field(Field1));
T2 := RTrim(Field(Field2));
Val(T1, A1, ErrCode);
Val(T2, A2, ErrCode);
A3 := A1 + A2;
Str(A3: Max(Fields^[Field1].FieldLen, Fields^[Field2].FieldLen): Max(Fields^[Field1].FieldDec, Fields^[Field2].FieldDec),
T3);
Add := LTrim(T3);
end;
procedure DBF.AddDBKey(NdxID: byte; KeyStr: DBKey);
begin
if UCKey then
KeyStr := Upper(KeyStr);
AddKey(Indexes^[NdxID].Ndx, DBRecNum, KeyStr);
end;
procedure DBF.AddDBRec; {Add new record, no index open.}
var
RecordNumber: longint;
begin
TotalRecs := TotalRecs + 1;
RecordNumber := TotalRecs;
DBRecNum := RecordNumber;
RecordNumber := (RecordNumber - 1) * Header^.RecordLen + Header^.Location;
Seek(DBFile, RecordNumber);
BlockWrite(DBFile, DBRecord^, Header^.RecordLen, ErrCode);
Dispose(DBRecord);
DBRecord := nil;
end;
function DBF.Allocated: boolean;
begin
Allocated := (DBRecord <> nil);
end;
procedure DBF.AppendBlank;
var
RecordNumber: longint;
begin
NewDBRec;
TotalRecs := TotalRecs + 1;
RecordNumber := TotalRecs;
DBRecNum := RecordNumber;
RecordNumber := (RecordNumber - 1) * Header^.RecordLen + Header^.Location;
Seek(DBFile, RecordNumber);
BlockWrite(DBFile, DBRecord^, Header^.RecordLen, ErrCode);
end;
procedure DBF.BailOut;
var
Message: string [80];
Number: string;
ID: byte;
begin
GotOne := True;
for ID := 1 to MaxInds do
if Indexes^[ID].Open then
CloseDBIndex(ID);
IndsOpen := False;
SetDBColor(White, Blue);
ClrScr;
case TPDBErr of
1: Message := 'Invalid DOS FUNCTION code !';
2: Message := 'File not found ! ' + IIF(Length(RTrim(LTrim(TErrorName))) <> 0, ' -- > ' + Upper(TErrorName), '');
3: Message := 'Path not found !';
4: Message := 'Too many open files !';
5: Message := 'File access denied !';
6: Message := 'Invalid file handle !';
8: Message := 'Not enough memory !';
9: Message := 'Too many open indexes !';
12: Message := 'Invalid file access code !';
15: Message := 'Invalid drive number !';
16: Message := 'Cannot remove current directory !';
17: Message := 'Cannot rename across drives !';
100: Message := 'Disk read error !';
101: Message := 'Disk write error !';
102: Message := 'File not assigned !';
103: Message := 'File not open !';
104: Message := 'File not open for input !';
105: Message := 'File not open for output !';
106: Message := 'Invalid numeric format !';
200: Message := 'Division by zero !';
201: Message := 'Range check error !';
202: Message := 'Stack overflow error !';
203: Message := 'Heap overflow error !';
204: Message := 'Invalid pointer operation !';
1000: Message := 'Record size is greater than 4000 chars !';
1002: Message := 'Specified Index Key Length is greater than 254 chars !';
1003: Message := 'Invalid DBF File structure !';
1004: Message := 'Index File created with different key size !';
1005: Message := 'Not enough memory for index page stack !';
end;
Beep;
Beep;
FlashC(8, White + BlueBG, 'TPDB Version 3.24');
FlashC(10, Yellow + BlueBG, 'ERROR !');
FlashC(12, White + RedBG, Message);
CursorOff;
FlashC(14, LightRed + BlueBG, 'Press any key to halt program....');
FlashC(16, LightCyan + BlueBG, 'Copyright 1989 Brian Corll');
repeat
until KeyPressed;
TErrorName := '';
TPDBErr := 0;
SetDBColor(White, Black);
ClrScr;
Halt(1);
end;
procedure Beep;
begin
Sound(1500);
Delay(50);
Sound(1000);
Delay(50);
NoSound;
end;
function DBF.BinSearch(FieldNo: byte; Position: integer; SearchKey: DBKey): longint;
{Implements a binary search for sorted files of unique elements }
var
Width: integer;
J, Low, High, Result: longint;
begin
Width := Length(SearchKey);
if Width < 1 then
Exit;
Low := 1;
High := TotalRecs;
while High >= Low do begin
J := (Low + High) div 2;
GetDBRec(J);
if SearchKey < Copy(Field(FieldNo), Position, Width) then
High := J - 1
else if SearchKey > Copy(Field(FieldNo), Position, Width) then
Low := J + 1
else begin
BinSearch := J;
Exit
end
end;
BinSearch := 0;
end;
function DBF.BOF: boolean;
begin
if IndsAreOpen then
BOF := not OK
else if DBRecNum = 1 then
BOF := True
else
BOF := False;
end;
function BoolToStr(Param: byte; IfTRUE, IfFALSE: char): string;
var
Temp: string;
begin
case Param of
0: Temp := Filler;
1: Temp := IfTRUE;
2: Temp := IfFALSE;
end;
BoolToStr := Temp;
end;
procedure CheckScreen(var CurrPos: byte; BC: char; Up, Down: CharSet; Low, High: byte);
begin
if (BC in Down) then
if CurrPos = High then
CurrPos := Low
else
Inc(CurrPos)
else if (BC in Up) then
if CurrPos = Low then
CurrPos := High
else
Dec(CurrPos)
end;
destructor DBF.Done;
var
EOFMarker: byte;
Z: byte;
begin
WriteDBHeader;
EOFMarker := $1A;
Seek(DBFile, Header^.Location + (Header^.RecCount * Header^.RecordLen));
BlockWrite(DBFile, EOFMarker, 1);
Close(DBFile);
Dec(FilesOpen);
if not MAlloc then begin
Dispose(Header);
Dispose(Fields);
end;
if Allocated then begin
Dispose(DBRecord);
DBRecord := nil;
end;
DBFOpen := False;
for Z := 1 to MaxInds do begin
if Indexes^[Z].Open then begin
CloseDBIndex(Z);
Indexes^[Z].Open := False;
end;
end;
if FromPack then
FromPack := False
else
Dispose(Indexes);
end;
procedure DBF.CloseDBIndex(NdxID: byte);
begin
if Indexes^[NdxID].Open then begin
CloseIndex(Indexes^[NdxID].Ndx);
Indexes^[NdxID].Open := False;
end;
Dec(FilesOpen);
end;
procedure CopyFile(Source, Dest: FileName);
{ Copies a .DBF file to another .DBF file }
type
FileBuffer = array [1..65521] of byte;
var
Buffer:^byte;
InFile, OutFile: file;
ErrorCode, BlocksRead, BlocksWritten: word;
Time: longint;
BufferSize: word;
begin
BufferSize := SizeOf(FileBuffer);
if (BufferSize > MaxAvail) then
BufferSize := MaxAvail;
GetMem(Buffer, BufferSize);
Assign(InFile, Source);
Reset(InFile, 1);
ErrorCode := IOResult;
GetFTime(InFile, Time);
if ErrorCode = 0 then begin
Assign(OutFile, Dest);
Rewrite(OutFile, 1);
ErrorCode := IOResult;
if ErrorCode = 0 then begin
repeat
BlockRead(InFile, Buffer^, BufferSize, BlocksRead);
BlockWrite(OutFile, Buffer^, BlocksRead, BlocksWritten);
if BlocksWritten < BlocksRead then
ErrorCode := 81;
until ((ErrorCode <> 0) or (BlocksRead < BufferSize));
SetFTime(OutFile, Time);
Close(OutFile);
if ErrorCode <> 0 then
Erase(OutFile);
end;
Close(InFile);
end;
FreeMem(Buffer, BufferSize);
end; { CopyFile }
procedure DBF.DBReset; {Reset dBASE file.}
begin {$I-}
Reset(DBFile, 1); {$I+}
if TPDBErr = 0 then
TPDBErr := IOResult;
if (TPDBErr <> 0) and (not GotOne) then begin
TErrorName := DBFName;
BailOut;
end;
end;
procedure DBF.DelDBKey(KeyStr: DBKey; NdxID: byte);
begin
if UCKey then
KeyStr := Upper(KeyStr);
DeleteKey(Indexes^[NdxID].Ndx, DBRecNum, KeyStr);
end;
function DBF.Deleted: boolean;
begin
if Mem[Seg(DBRecord^):Ofs(DBRecord^)+1] = $2A then
Deleted := True
else
Deleted := False;
end;
procedure DBF.Display;
var
FNo: byte;
K: integer;
begin
ClrScr;
for FNo := 1 to NumFields do begin
for K := 1 to 11 do
Write(Fields^[FNo].FieldName[K]);
Write(': ');
if Chr(Ord(Fields^[FNo].FieldType)) = 'D' then
Write(FormDate(Field(FNo)))
else
Write(Field(FNo));
Writeln;
if FNo mod 23 = 0 then begin
Wait;
ClrScr;
end;
end;
end;
function DBF.Divide(Field1, Field2: byte): string; (* Divide field1 BY field 2 *)
var
T1, T2, T3: string;
D1, D2, D3: real;
begin
T1 := RTrim(Field(Field1));
T2 := RTrim(Field(Field2));
Val(T1, D1, ErrCode);
Val(T2, D2, ErrCode);
D3 := D1 / D2;
Str(D3: Max(Fields^[Field1].FieldLen, Fields^[Field2].FieldLen): Max(Fields^[Field1].FieldDec, Fields^[Field2].FieldDec),
T3);
Divide := LTrim(T3);
end;
function DBF.DBEOF: boolean;
begin
if IndsAreOpen and (CurrNdx > 0) then
DBEOF := not OK
else
DBEOF := (DBRecNum > TotalRecs);
end;
function DBF.Field(FNo: byte): string;
var
Temp: string;
begin
Temp[0] := Chr(Ord(Fields^[FNo].FieldLen));
Move(Mem[Seg(DBRecord^):Ofs(DBRecord^)+Fields^[FNo].FieldAddress], Temp[1], Fields^[FNo].FieldLen);
Temp := PadR(Temp, Fields^[FNo].FieldLen);
Field := Temp;
end;
procedure DBF.FillRecs(NumRecs: longint);
var
J: longint;
begin
if TotalRecs > 0 then
GoBottom;
for J := 1 to NumRecs do begin
NewDBRec;
AddDBRec;
end;
end;
procedure DBF.Find(NdxID: byte; SearchStr: string);
begin
FindKey(Indexes^[NdxID].Ndx, DBRecNum, SearchStr);
if OK then begin
GetDBRec(DBRecNum);
Found := True;
end else
Found := False;
end;
procedure FlashFill(Row, Col, Rows, Cols, Attr: byte; Ch: char);
var
Z: byte;
Temp: string;
begin
Temp := Replicate(Ch, Cols);
for Z := Row to Row + Rows - 1 do
Flash(Z, Col, Attr, Temp);
end;
procedure DBF.FlushDB;
begin
MAlloc := True;
Done;
MAlloc := False;
DBReset;
end;
procedure DBF.Get(FNo, X, Y: byte);
var
TempStr1: string;
procedure Character;
begin
TempStr1 := Field(FNo);
BC := GetString(TempStr1, Fields^[FNo].FieldLen, Y, X);
Repl(FNo, TempStr1);
TempStr1 := PadR(TempStr1, Fields^[FNo].FieldLen);
Flash(X, Y, Normal, Tempstr1);
end; {PROCEDURE Character}
procedure Numeric;
var
NumLen: byte;
TempInt: longint;
TempReal: real;
RealStr, IntStr: string;
begin
NumLen := Fields^[FNo].FieldLen;
Decimals := Fields^[FNo].FieldDec; {If field is a real number}
if Decimals > 0 then begin
RealStr := '';
TempReal := 0;
RealStr := Field(FNo);
Val(RealStr, TempReal, ErrCode);
BC := GetReal(TempReal, MinReal, MaxReal, NumLen, Y, X);
Str(TempReal: NumLen: Decimals, RealStr);
Repl(FNo, RealStr);
Flash(X, Y, Normal, RealStr);
end else {Otherwise, it's an integer value}
begin
IntStr := '';
TempInt := 0;
IntStr := Field(FNo);
Val(IntStr, TempInt, ErrCode);
BC := GetLongInt(TempInt, MinLong, MaxLong, NumLen, Y, X);
Str(TempInt: NumLen, IntStr);
Repl(FNo, IntStr);
Flash(X, Y, Normal, IntStr);
end;
end; {PROCEDURE Numeric}
procedure Dates;
var
TempDate, TmpDat2: string [8];
MM, DD, DC: byte;
YY, GG: integer;
TM, TD, TY, Month, Day: string [2];
Year: string [4];
begin
TempDate := '';
TempDate := Field(FNo);
repeat
Year := Copy(TempDate, 1, 4);
Month := Copy(TempDate, 5, 2);
Day := Copy(TempDate, 7, 2);
Val(Year, YY, ErrCode);
Val(Month, MM, ErrCode);
Val(Day, DD, ErrCode);
if YY >= 1900 then
YY := YY - 1900;
case DateFormat of
American: begin
BC := GetByte(MM, 0, 12, 2, Y, X);
BC := GetByte(DD, 0, 31, 2, Y + 3, X);
BC := GetInteger(YY, 0, 99, 2, Y + 6, X);
end;
French: begin
BC := GetByte(DD, 0, 31, 2, Y, X);
BC := GetByte(MM, 0, 12, 2, Y + 3, X);
BC := GetInteger(YY, 0, 99, 2, Y + 6, X);
end;
Italian: begin
BC := GetByte(DD, 0, 31, 2, Y, X);
BC := GetByte(MM, 0, 12, 2, Y + 3, X);
BC := GetInteger(YY, 0, 99, 2, Y + 6, X);
end;
German: begin
BC := GetByte(DD, 0, 31, 2, Y, X);
BC := GetByte(MM, 0, 12, 2, Y + 3, X);
BC := GetInteger(YY, 0, 99, 2, Y + 6, X);
end;
Ansi: begin
BC := GetInteger(YY, 0, 99, 2, Y, X);
BC := GetByte(MM, 0, 12, 2, Y + 3, X);
BC := GetByte(DD, 0, 31, 2, Y + 6, X);
end;
British: begin
BC := GetByte(DD, 0, 31, 2, Y, X);
BC := GetByte(MM, 0, 12, 2, Y + 3, X);
BC := GetInteger(YY, 0, 99, 2, Y + 6, X);
end;
end;
Str(MM, Month);
Str(DD, Day);
YY := YY + 1900;
Str(YY: 4, Year);
if DD < 10 then
Day := '0' + Day;
if MM < 10 then
Month := '0' + Month;
TempDate := Year + Month + Day;
if not ValidDate(TempDate) then
Beep;
case DateFormat of
American: begin
TmpDat2 := Copy(TempDate, 5, 2) + '/' + Copy(TempDate, 7, 2) + '/' + Copy(TempDate, 3, 2);
end;
French: begin
TmpDat2 := Copy(TempDate, 7, 2) + '/' + Copy(TempDate, 5, 2) + '/' + Copy(TempDate, 3, 2)
end;
Italian: begin
TmpDat2 := Copy(TempDate, 7, 2) + '-' + Copy(TempDate, 5, 2) + '-' + Copy(TempDate, 3, 2)
end;
German: begin
TmpDat2 := Copy(TempDate, 7, 2) + '.' + Copy(TempDate, 5, 2) + '.' + Copy(TempDate, 3, 2)
end;
Ansi: begin
TmpDat2 := Copy(TempDate, 3, 2) + '.' + Copy(TempDate, 5, 2) + '.' + Copy(TempDate, 7, 2)
end;
British: begin
TmpDat2 := Copy(TempDate, 7, 2) + '/' + Copy(TempDate, 5, 2) + '/' + Copy(TempDate, 3, 2)
end;
end;
Flash(X, Y, Normal, TmpDat2);
until ValidDate(TempDate);
Repl(FNo, TempDate);
end; {PROCEDURE Dates}
procedure Logical;
var
BoolVAR: byte;
TF: string [1];
begin
case Mem[Seg(DBRecord^):Ofs(DBRecord^)+Fields^[FNo].FieldAddress] of
Ord('Y'): BoolVAR := 1;
Ord('N'): BoolVAR := 2 else BoolVAR := 0;
end;
BC := GetBoolean(BoolVAR, 'Y', 'N', Y, X);
TF := BoolToStr(BoolVAR, 'Y', 'N');
Mem[Seg(DBRecord^):Ofs(DBRecord^)+Fields^[FNo].FieldAddress] := Ord(TF[1]);
Flash(X, Y, Normal, TF);
end;
var
Z: byte;
begin {PROCEDURE Get}
case Chr(Ord(Fields^[FNo].FieldType)) of
'C': Character;
'L': Logical;
'N': Numeric;
'D': Dates;
end;
end; {PROCEDURE Get}
function GetBoolean(var Param: byte; IfTRUE, IfFALSE: char; X, Y: byte): char;
var
BC: char;
Temp: string;
Value: byte;
begin
Value := Param;
Temp := BoolToStr(Value, IfTRUE, IfFALSE);
UpperCase := True;
Temp := Input(Temp, [IfTRUE, IfFALSE], 1, X, Y, BC);
if Length(Temp) = 0 then begin
Param := 0;
Flash(Y, X, Normal, BoolToStr(Param, IfTRUE, IfFALSE));
end else begin
if Temp = Filler then
Param := 0;
if Temp = IfTRUE then
Param := 1;
if Temp = IfFALSE then
Param := 2;
end;
UpperCase := False;
GetBoolean := BC;
end;
function GetByte(var Param: byte; LowLim, UpLim, Len, X, Y: byte): char;
var
BC: char;
WW, WL, WH: longint;
begin
WW := longint(Param);
WL := longint(LowLim);
WH := longint(UpLim);
BC := GetLongInt(WW, WL, WH, Len, X, Y);
Param := byte(WW);
GetByte := BC;
end;
procedure DBF.GetDBRec(RecordNumber: longint);
begin
if not Allocated then begin
GetMem(DBRecord,Header^.RecordLen);
end else begin
FreeMem(DBRecord,Header^.RecordLen);
DBRecord := nil;
GetMem(DBRecord,Header^.RecordLen)
end;
DBRecNum := RecordNumber;
RecordNumber := (RecordNumber - 1) * Header^.RecordLen + Header^.Location;
Seek(DBFile, RecordNumber);
BlockRead(DBFile, DBRecord^, Header^.RecordLen, ErrCode);
end;
function DBF.GetField(RecordNo: longint; FNo: byte): string;
type
FldArray = array [1..254] of char;
var
TempArray: FldArray;
FldAddr, RecordNumber: longint;
Temp: string [254];
K: byte;
begin
if FNo = 1 then
FldAddr := 1
else begin
FldAddr := 1;
for K := 1 to FNo - 1 do
FldAddr := FldAddr + Fields^[K].FieldLen;
end;
RecordNumber := (RecordNo - 1) * Header^.RecordLen + Header^.Location + FldAddr;
Seek(DBFile, RecordNumber);
BlockRead(DBFile, TempArray, Fields^[FNo].FieldLen, ErrCode);
Temp := '';
for K := 1 to Fields^[FNo].FieldLen do
Temp := Temp + TempArray[K];
GetField := Temp;
end;
function GetInteger(var Param: integer; LowLim, UpLim: integer; Len, X, Y: byte): char;
var
BC: char;
WW, WL, WH: longint;
begin
WW := longint(Param);
WL := longint(LowLim);
WH := longint(UpLim);
BC := GetLongInt(WW, WL, WH, Len, X, Y);
Param := integer(WW);
GetInteger := BC;
end;
function GetLongInt(var Param: longint; LowLim, UpLim: longint; Len, X, Y: byte): char;
var
Temp: string;
P, Value: longint;
I: integer;
Err: boolean;
BC: char;
begin
repeat
Err := False;
Str(Param, Temp);
Temp := Input(Temp, ['0'..'9'], Len, X, Y, BC);
Val(Temp, P, I);
if Length(Temp) = 0 then
Value := 0
else if I = 0 then
Value := P
else begin
Value := Param;
Beep;
Err := True;
end;
if (not ((Value >= LowLim) and (Value <= UpLim))) then
Beep;
until (Value >= LowLim) and (Value <= UpLim) and (not (Err));
Param := Value;
GetLongInt := BC;
end;
function GetReal(var Param: real; LowLim, UpLim: real; Len, X, Y: word): char;
var
Temp: string;
P, Value: real;
I: word;
Err: boolean;
BC: char;
begin
repeat
Err := False;
Temp := RealToStr(Param);
Temp := Input(Temp, ['0'..'9', '.', '-'], Len, X, Y, BC);
Val(Temp, P, I);
if Length(Temp) = 0 then
Value := 0.0
else if I = 0 then
Value := P
else begin
Value := Param;
Beep;
Err := True;
end;
if (not ((Value >= LowLim) and (Value <= UpLim))) then
Beep;
until (Value >= LowLim) and (Value <= UpLim) and (not (Err));
Param := Value;
GetReal := BC;
end;
function GetString(var Param: string; Len, X, Y: byte): char;
var
Temp: string;
BC: char;
begin
Temp := Param;
Temp := Input(Temp, [#32..#126], Len, X, Y, BC);
Param := Temp;
GetString := BC;
end;
function GetWord(var Param: word; LowLim, UpLim: word; Len, X, Y: byte): char;
var
BC: char;
WW, WL, WH: longint;
begin
WW := longint(Param);
WL := longint(LowLim);
WH := longint(UpLim);
BC := GetLongInt(WW, WL, WH, Len, X, Y);
Param := word(WW);
GetWord := BC;
end;
procedure DBF.GoBottom;
Var KeyStr : String;
begin
If CurrNdx <> 0 then
begin
ClearKey(Indexes^[CurrNdx].Ndx);
PrevKey(Indexes^[CurrNdx].Ndx, DBRecNum, KeyStr);
GetDBRec(DBRecNum);
end
else
GetDBRec(Header^.RecCount);
end;
procedure DBF.GoTop;
Var KeyStr : String;
begin
If CurrNdx <> 0 then
begin
ClearKey(Indexes^[CurrNdx].Ndx);
NextKey(Indexes^[CurrNdx].Ndx, DBRecNum, KeyStr);
GetDBRec(DBRecNum);
end
else
GetDBRec(1);
end;
function DBF.IIF(BoolVAR: boolean; IfTRUE, IfFALSE: string): string;
begin
if BoolVAR then
IIF := IfTRUE
else
IIF := IfFALSE;
end;
function DBF.IndsAreOpen: boolean;
var
J: byte;
begin
IndsAreOpen := False;
for J := 1 to MaxInds do
if Indexes^[J].Open then begin
IndsAreOpen := True;
Exit;
end;
end;
procedure DBF.IndexOn(NdxID: byte; NdxName: FileName; NdxField: byte; DupFlag: byte);
var
RecNumber: longint;
begin
MakeDBIndex(NdxID, NdxName, Fields^[NdxField].FieldLen, DupFlag);
OpenDBIndex(NdxID, NdxName, Fields^[NdxField].FieldLen, DupFlag);
for RecNumber := 1 to TotalRecs do begin
GetDBRec(RecNumber);
if not Deleted then
AddDBKey(NdxID, Field(NdxField));
end;
end;
constructor DBF.Init(DBName: FileName);
var
NdxID: byte;
begin
Inc(FilesOpen);
New(Header);
New(Fields);
New(Indexes);
DBFName := RTrim(LTrim(DBName));
Assign(DBFile, DBFName); {$I-}
Reset(DBFile, 1); {$I+}
TPDBErr := IOResult;
if (TPDBErr <> 0) and (not GotOne) then begin
TErrorName := DBName;
BailOut;
end;
DBFOpen := True;
DBRecNum := 1;
for NdxID := 1 to MaxInds do begin
Indexes^[NdxID].NdxName := '';
Indexes^[NdxID].Open := False;
Indexes^[NdxID].NdxID := 0;
end;
CurrNdx := 0;
ReadDBHeader;
GetMem(DBRecord,Header^.RecordLen);
end;
function Input(var S: string; Term: CharSet; L, X, Y: byte; var BC: char): string;
const
Next: CharSet = [Return, CursorUp, CursorDown, PageUp, PageDown, Escape];
var
P: byte;
Ch: char;
Temp: string;
begin
CursorOn;
if S = '0' then
S[0] := #0;
Temp := Replicate(Filler, L - Length(S));
Temp := Concat(S, Temp);
Flash(Y, X, Reverse, Temp);
P := 0;
repeat
GotoXY(X + P, Y);
Ch := ReadChar;
if UpperCase then
CH := UpCase(CH);
if (CH in Term) then begin
if P < L then begin
if Length(S) = L then
Delete(S, L, 1);
Inc(P);
Insert(CH, S, P);
Write(Copy(S, P, L));
if AutoWrap and (P = L) then
Ch := Return;
end else if not (AutoWrap) then
Beep;
end else
case CH of
^H, #127: if P > 0 then begin
Delete(S, P, 1);
Write(^H, Copy(S, P, L), Filler);
Dec(P);
end else
Beep;
DelKey: if P < Length(S) then begin
Delete(S, Succ(P), 1);
Write(Copy(S, Succ(P), L), Filler);
end;
CursorLeft: if P > 0 then
Dec(P)
else
Beep;
CursorRight: if P < Length(S) then
Inc(P)
else
Beep;
CursorHome: P := 0;
CursorEND: P := Length(S);
^Y: begin
Write(Replicate(Filler, Length(S) - P));
Delete(S, Succ(P), L);
end;
end;
until CH in Next;
P := Length(S);
Input := S;
BC := CH;
CursorOff;
end;
function IntToStr(Number: longint): string;
var
Temp: string;
begin
Str(Number, Temp);
IntToStr := RTrim(LTrim(Temp));
end;
function DBF.Locate(FieldNo: byte; SearchStr: string): boolean;
var
RecNumber: longint;
begin
DBReset;
RecNumber := 1;
while RecNumber <= TotalRecs do begin
GetDBRec(RecNumber);
if Pos(SearchStr, IIF(UCKey, Upper(Field(FieldNo)), Field(FieldNo))) > 0 then begin
Locate := True;
Exit;
end;
RecNumber := RecNumber + 1;
end;
Locate := False;
end;
procedure DBF.LookUp(SearchStr: string; NdxID: byte);
begin
SearchKey(Indexes^[NdxID].Ndx, DBRecNum, SearchStr);
if OK then begin
GetDBRec(DBRecNum);
Found := True;
end else
Found := False;
end;
procedure DBF.MakeDBIndex(NdxID: byte; DBIndexName: FileName; KeyLen, Status: integer);
begin
MakeIndex(Indexes^[NdxID].Ndx, DBIndexName, KeyLen, Status);
Indexes^[NdxID].NdxName := DBIndexName;
Indexes^[NdxID].NdxID := NdxID;
Indexes^[NdxID].Open := True;
CloseDBIndex(NdxID);
end;
procedure DBF.Mark;
begin
Mem[Seg(DBRecord^):Ofs(DBRecord^)+1] := $2A;
end; {Mark}
function Max(N1, N2: integer): integer;
begin
if N1 > N2 then
Max := N1
else
Max := N2;
end; {Max}
function Min(N1, N2: integer): integer;
begin
if N1 < N2 then
Min := N1
else
Min := N2;
end; {Min}
function DBF.Mul(Field1, Field2: byte): string; (* Multiply field 1 and field2 *)
var
T1, T2, T3: string;
M1, M2, M3: real;
ErrCode: integer;
begin
T1 := RTrim(Field(Field1));
T2 := RTrim(Field(Field2));
Val(T1, M1, ErrCode);
Val(T2, M2, ErrCode);
M3 := M1 * M2;
Str(M3: Max(Fields^[Field1].FieldLen, Fields^[Field2].FieldLen): Max(Fields^[Field1].FieldDec, Fields^[Field2].FieldDec),
T3);
Mul := LTrim(T3);
end; {Mul}
procedure DBF.NewDBRec;
begin
if not Allocated then begin
GetMem(DBRecord,Header^.RecordLen)
end else begin
FreeMem(DBRecord,Header^.RecordLen);
DBRecord := nil;
GetMem(DBRecord,Header^.RecordLen);
end;
FillChar(DBRecord^, Header^.RecordLen, #32);
DBRecNum := TotalRecs + 1;
end; {NewDBRec}
procedure DBF.NextDBKey(NdxID: byte; KeyStr: DBKey);
begin
if UCKey then
KeyStr := Upper(KeyStr);
NextKey(Indexes^[NdxID].Ndx, DBRecNum, KeyStr);
GetDBRec(DBRecNum);
end; {NextDBKey}
procedure DBF.NextRec;
begin
GetDBRec(DBRecNum + 1);
end; {NextRec}
procedure DBF.OpenDBIndex(NdxID: byte; DBIndexName: FileName; KeyLen, Status: integer);
begin
OpenIndex(Indexes^[NdxID].Ndx, DBIndexName, KeyLen, Status);
Indexes^[NdxId].NdxName := DBIndexName;
Indexes^[NdxID].NdxID := NdxId;
Indexes^[NdxID].Open := True;
Inc(FilesOpen);
SetIndexTo(NdxID);
end; {OpenDBIndex}
procedure DBF.Pack;
var
FNo: byte;
J, TRec: longint;
begin
MAlloc := True;
Done;
Malloc := False;
FromPack := True;
DBReset;
ReadDBHeader;
TRec := 1;
for J := 1 to TotalRecs do begin
GetDBRec(J);
if not Deleted then
begin
PutDBRec(TRec);
TRec := TRec + 1;
end;
end;
Done;
Init(DBFName);
TotalRecs := TRec - 1;
WriteDBHeader;
end; {Pack}
procedure DBF.PrevDBKey(NdxID: byte; KeyStr: DBKey);
begin
if UCKey then
KeyStr := Upper(KeyStr);
PrevKey(Indexes^[NdxID].Ndx, DBRecNum, KeyStr);
GetDBRec(DBRecNum);
end; {PrevDBKey}
procedure DBF.PrevRec;
begin
GetDBRec(DBRecNum - 1);
end; {PrevRec}
procedure Prompt(Row, Col: byte; PromptStr: Str80);
begin
Flash(Row, Col, Normal, PromptStr);
end; {Prompt}
procedure DBF.PutDBRec(RecordNumber: longint);
begin
DBRecNum := RecordNumber;
RecordNumber := (RecordNumber - 1) * Header^.RecordLen + Header^.Location;
Seek(DBFile, RecordNumber);
BlockWrite(DBFile, DBRecord^, Header^.RecordLen, ErrCode);
FreeMem(DBRecord,Header^.RecordLen);
DBRecord := nil;
end; {PutDBRec}
function ReadChar: char;
var
CH: char;
begin
ReadKb(ExtKey, CH);
if ExtKey then begin
case CH of
#75: CH := CursorLeft;
#77: CH := CursorRight;
#72: CH := CursorUp;
#80: CH := CursorDown;
#73: CH := PageUp;
#81: CH := PageDown;
#71: CH := CursorHome;
#79: CH := CursorEND;
#83: CH := DelKey;
else CH := #0;
end;
if CH = #9 then
CH := TabKey;
end;
ReadChar := CH;
end; {ReadChar}
procedure DBF.ReadDBHeader;
(*Read a .DBF header.*)
var
FNo: byte;
FAddr: longint;
begin
BlockRead(DBFile, Header^, 32, ErrCode);
TotalRecs := Header^.RecCount;
NumFields := (Header^.Location - 33) div 32;
FAddr := 1;
for FNo := 1 to NumFields do begin
BlockRead(DBFile, Fields^[FNo], 32, ErrCode);
Fields^[FNo].FieldAddress := FAddr;
FAddr := FAddr + Fields^[FNo].FieldLen;
end;
end; (*ReadDBHeader*)
procedure ReadKB(var ExtKey: boolean; var Ch: char);
begin
ExtKey := False;
Ch := ReadKey;
if Ch = #0 then begin
ExtKey := True;
Ch := ReadKey;
end;
end; {ReadKB}
function RealToStr(Number: real): string;
var
Temp: string;
I: word;
begin
Str(Number: NumLen: Decimals, Temp);
Temp := LTrim(Temp);
I := Length(Temp);
while Temp[I] = '0' do
Dec(I);
if Temp[I] = '.' then
Dec(I);
RealToStr := Copy(Temp, 1, I);
end; {RealToStr}
procedure DBF.Recall;
begin
Mem[Seg(DBRecord^):Ofs(DBRecord^)+1] := $20;
end; {Recall}
function DBF.RecCount: longint;
begin
RecCount := TotalRecs;
end;
function DBF.RecNo: longint;
begin
RecNo := DBRecNum;
end;
procedure DBF.Repl(FNo: Byte; InStr: string);
var
Temp: string;
begin
Temp := PadR(InStr, Fields^[FNo].FieldLen);
Move(Temp[1], Mem[Seg(DBRecord^): Ofs(DBRecord^) + Fields^[FNo].FieldAddress], Fields^[FNo].FieldLen);
end; {Repl}
procedure DBF.ReplEach(FNo: byte; InStr: string);
var
J: longint;
begin
DBReset;
for J := 1 to TotalRecs do begin
GetDBrec(J);
Repl(FNo, InStr);
PutDBRec(J);
end;
end; {ReplEach}
procedure DBF.Save;
begin
PutDBRec(DBRecNum);
end; {Save}
procedure DBF.Say(FNo, Row, Col: byte);
var
GG: integer;
TempStr: string;
Bool: char;
TempDate: string [8];
Month, Day, Year: string [2];
YY: integer;
MM, DD: byte;
Slush: string [8];
begin
case Chr(Ord(Fields^[FNo].FieldType)) of
'C', 'N': begin
TempStr := '';
for GG := Fields^[FNo].FieldAddress to Fields^[FNo].FieldAddress+Fields^[FNo].FieldLen-1 do
TempStr := TempStr + Chr(Mem[Seg(DBRecord^):Ofs(DBRecord^)+GG]);
Flash(Row, Col, Normal, TempStr);
end;
'L': begin
Bool := Chr(Mem[Seg(DBRecord^):Ofs(DBRecord^)+Fields^[FNo].FieldAddress]);
Flash(Row, Col, Normal, Bool);
end;
'D': begin
TempDate := '';
Slush := '';
case DateFormat of
American: begin
Slush := Field(FNo);
TempDate := Copy(Slush, 5, 2) + '/' + Copy(Slush, 7, 2) + '/' + Copy(Slush, 3, 2);
end;
Ansi: begin
Slush := Field(FNo);
TempDate := Copy(Slush, 3, 2) + '.' + Copy(Slush, 5, 2) + '.' + Copy(Slush, 7, 2);
end;
British: begin
Slush := Field(FNo);
TempDate := Copy(Slush, 7, 2) + '/' + Copy(Slush, 5, 2) + '/' + Copy(Slush, 3, 2);
end;
French: begin
Slush := Field(FNo);
TempDate := Copy(Slush, 7, 2) + '/' + Copy(Slush, 5, 2) + '/' + Copy(Slush, 3, 2);
end;
German: begin
Slush := Field(FNo);
TempDate := Copy(Slush, 7, 2) + '.' + Copy(Slush, 5, 2) + '.' + Copy(Slush, 3, 2);
end;
Italian: begin
Slush := Field(FNo);
TempDate := Copy(Slush, 7, 2) + '-' + Copy(Slush, 5, 2) + '-' + Copy(Slush, 3, 2);
end;
end;
Flash(Row, Col, Normal, TempDate);
end;
end;
end; {Say}
procedure SetDateFormat(Format: byte);
begin
DateFormat := Format;
end;
procedure SetDBColor(FG, BG: byte);
begin
TextColor(FG);
TextBackground(BG);
end; {SetDBColor}
procedure DBF.SetIndexTo(NdxID : Byte);
begin
CurrNdx := NdxID;
end;
procedure DBF.ShowStatus; {Display .DBF status.}
var
FNo, K: byte;
begin
ClrScr;
Writeln('File name is ', Upper(DBFName), '.');
Writeln('Last update was on ', Header^.Month, '/', Header^.Day, '/', Header^.Year, '.');
Writeln('Number of records is ', Header^.RecCount, '.');
Writeln('Data starts at byte # ', Header^.Location, '.');
Writeln('Record length is ', Header^.RecordLen, ' bytes.');
Writeln('There are ', NumFields, ' fields.');
Wait;
for FNo := 1 to NumFields do begin
Write('Field # ', FNo: 2, ': ');
for K := 1 to 11 do
Write(Fields^[FNo].FieldName[K]);
Write(' Type: ', Chr(Fields^[FNo].FieldType));
Write(' Length: ', Fields^[FNo].FieldLen: 3);
if Chr(Ord(Fields^[FNo].FieldType)) = 'N' then
Write(' Decimals: ', Fields^[FNo].FieldDec: 2);
Writeln;
if FNo mod 20 = 0 then
Wait;
end;
Wait;
DBReset;
end; {ShowStatus}
procedure DBF.Skip(NumRecs : Longint);
Var KeyStr : String;
N : Longint;
begin
If CurrNdx <> 0 then
begin
If NumRecs = 1 then
begin
NextKey(Indexes^[CurrNdx].Ndx, DBRecNum, KeyStr);
If not OK then Exit;
GetDBRec(DBRecNum);
end;
If NumRecs > 1 then
begin
For N := DBRecNum to DBRecNum + NumRecs do
begin
NextKey(Indexes^[CurrNdx].Ndx, DBRecNum, KeyStr);
If not OK then Exit;
end;
GetDBRec(DBRecNum);
end;
If NumRecs < 0 then
begin
For N := DBRecNum downto (DBRecNum + NumRecs - 1) do
begin
PrevKey(Indexes^[CurrNdx].Ndx, DBRecNum, KeyStr);
If not OK Then Exit;
end;
GetDBRec(DBRecNum);
end;
end
else
begin
GetDBRec(DBRecNum + NumRecs);
end;
end; {Skip}
function DBF.Sub(Field1, Field2: byte): string; (* Subtract field 2 FROM field 1 *)
var
T1, T2, T3: string;
S1, S2, S3: real;
ErrCode: integer;
begin
T1 := RTrim(Field(Field1));
T2 := RTrim(Field(Field2));
Val(T1, S1, ErrCode);
Val(T2, S2, ErrCode);
S3 := S1 - S2;
Str(S3: Max(Fields^[Field1].FieldLen, Fields^[Field2].FieldLen): Max(Fields^[Field1].FieldDec, Fields^[Field2].FieldDec),
T3);
Sub := LTrim(T3);
end; {Sub}
function DBF.Sum(FNo: byte): real;
{Sums a numeric field. If specified field is not numeric returns 0.}
var
J: longint;
TempStr: string;
TempReal: real;
EC: integer;
TotalSum: real;
begin
if Chr(Ord(Fields^[FNo].FieldType)) <> 'N' then begin
Sum := 0;
Exit;
end else begin
DBReset;
TotalSum := 0;
for J := 1 to TotalRecs do begin
GetDBRec(J);
TempStr := RTrim(LTrim(Field(FNo)));
Val(TempStr, TempReal, EC);
TotalSum := TotalSum + TempReal;
end;
end;
Sum := TotalSum;
end; {Sum}
procedure Wait;
begin
Writeln('Press any key to continue...');
Ch := ReadKey;
end; {Wait}
procedure DBF.WriteDBHeader;
{Update .DBF header.}
begin
DBReset;
GetDate(Y, M, D, DW);
Y := Y - 1900;
Header^.Year := Y;
Header^.Month := M;
Header^.Day := D;
Header^.RecCount := TotalRecs;
BlockWrite(DBFile, Header^, 32, ErrCode);
end; {WriteDBHeader}
procedure DBF.Zap;
var
FNo: byte;
begin
Rewrite(DBFile, 1);
TotalRecs := 0;
Header^.RecCount := 0;
BlockWrite(DBFile, Header^, 32, ErrCode);
for FNo := 1 to NumFields do begin
BlockWrite(DBFile, Fields^[FNo], 32, ErrCode);
end;
Header^.Terminator := Chr(Ord($0D));
BlockWrite(DBFile, Header^.Terminator, 1, ErrCode);
DBReset;
end; {Zap}
begin {TPDB}
SetDateFormat(American);
FromPack := False;
TAErrorProc := @DBF.BailOut;
TErrorName := '';
TPDBErr := 0;
FilesOpen := 0;
end. {TPDB}
{END of Source Code - TPDB.pas Version 3.35 Copyright 1988 - 1992 Brian Corll }